package org.efficientsoap.client;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import android.util.Log;
import android.util.Xml;

/**
 * @author Christian Rau 
 * @param <T>
 * 
 * @param <T>
 */
public class EfficientSoapSerializer<T> {

	/**
	 * Namespace for envelope 2003/05
	 * */
	public static final String ENVELOPE0503 = "http://www.w3.org/2003/05/soap-envelope";

	/**
	 * Namespace for encoding 2003/05
	 */
	public static final String ENCODING0503 = "http://www.w3.org/2003/05/soap-encoding";

	/**
	 * Namespace for envelope
	 * */
	public static final String ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/";

	/**
	 * Namespace for encoding
	 */
	public static final String ENCODING = "http://schemas.xmlsoap.org/soap/encoding/";

	/**
	 * Namespace for XML Schema
	 */
	public static final String XSD = "http://www.w3.org/2001/XMLSchema";

	/**
	 * Namespace for XMLSchema instance
	 */
	public static final String XSI = "http://www.w3.org/2001/XMLSchema-instance";

	/**
	 * Namespace for XML Schema 1999
	 */
	public static final String XSD1999 = "http://www.w3.org/1999/XMLSchema";

	/**
	 * Namespace for XMLSchema-instance 1999
	 */
	public static final String XSI1999 = "http://www.w3.org/1999/XMLSchema-instance";

	/**
	 * constant for SOAP Version 1.0
	 */
	public static final int VERSION10 = 10;

	/**
	 * constant for SOAP Version 1.1
	 */
	public static final int VERSION11 = 11;

	/**
	 * constant for SOAP Version 1.2
	 */
	public static final int VERSION12 = 12;

	private static final String APP_LOGGING_TAG = EfficientSoapSerializer.class
			.getSimpleName();
	
	private int soapVersion = VERSION10;
	private String soapNamespaceURI = null;
	private String XMLSchemaURI = null;
	private String XMLSchemaInstanceURI = null;
	private String encodingURI = null;
	
	public class Namespace{		
		String prefix;
		String namespaceURI;		
		public Namespace(String prefix, String namespaceURI) {
			this.prefix = prefix;
			this.namespaceURI = namespaceURI;
		}
	}
	private ArrayList<Namespace> envNamespaces = new ArrayList<EfficientSoapSerializer<T>.Namespace>();
	
	protected XmlSerializer serializer;
	private T object2serialize;
	protected ArrayList<EfficientSoapParam<?>> paramList= new ArrayList<EfficientSoapParam<?>>();
	private IBodySerializer<T> bodySerializer;
	private IHeaderSerializer headerSerializer;

	public String getSoapNamespaceURI() {
		return this.soapNamespaceURI;
	}
	
	public String getXMLSchemaURI() {
		return XMLSchemaURI;
	}

	public String getXMLSchemaInstanceURI() {
		return XMLSchemaInstanceURI;
	}

	public String getEncodingURI() {
		return encodingURI;
	}

	
	public void setBodySerializer(IBodySerializer<T> bodySerializer) {
		this.bodySerializer = bodySerializer;
	}

	public void setHeaderSerializer(IHeaderSerializer headerSerializer) {
		this.headerSerializer = headerSerializer;
	}

	public int getSoapVersion() {
		return soapVersion;
	}

	public void setSoapVersion(int soapVersion) {
		this.soapVersion = soapVersion;
	}
	
	public T getObject2serialize() {
		return object2serialize;
	}

	public void setObject2serialize(T object2serialize) {
		this.object2serialize = object2serialize;
	}
	
	public void addParam(String tag, T param2serialize) {
		this.paramList.add(new EfficientSoapParam<T>(tag, param2serialize));
	}
	
	public ArrayList<EfficientSoapParam<?>> getParamList() {
		return paramList;
	}

	public XmlSerializer getSerializer() {
		return serializer;
	}
	

	public EfficientSoapSerializer() {
		setBodySerializer(new DefaultBodySerializerImpl());
		setHeaderSerializer(new DefaultHeaderSerializerImpl());
	}

	public void process(Writer out) throws IOException {

		this.serializer = Xml.newSerializer();
		
		serializer.setOutput(out);
		serializer.startDocument("UTF-8", false);// encoding="UTF-8" and standalone="no" it can only set here

		serializeEnvelope();

		serializer.endDocument();
		//Log.v(APP_LOGGING_TAG, bw.toString());
		serializer.flush();
		
		
	}
	
	/**
	 * @param prefix
	 * @param namespaceURI
	 * 
	 * Add an additional domain specific name-space to the envelope 
	 * NOTE: this method MUST be called before process()
	 */
	public void addNamespaceToEnvelope(String prefix ,String namespaceURI){
		this.envNamespaces.add(new Namespace(prefix, namespaceURI));
		
	}
	
	/**
	 * @throws IllegalArgumentException
	 * @throws IllegalStateException
	 * @throws IOException
	 * set namespaces and there prefixes to the envelope
	 * NOTE: this method is called in serializeEnvelope()
	 */
	private void addDomainNamespacesToEnv() throws IllegalArgumentException, IllegalStateException, IOException{	
		if(envNamespaces!=null){
			for (Namespace ns : envNamespaces){
			serializer.setPrefix(ns.prefix, ns.namespaceURI);
			}
		}		
	}

	// Primitive Object serializing methods
	public void serializeStringElem(String str, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		if (null != tag) {
			serializer.startTag(null, tag);
			serializer.text(str);
			serializer.endTag(null, tag);
		} else {
			serializer.text(str);
		}
	}

	public void serializeIntegerElem(int i, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		if (null != tag) {
			serializer.startTag(null, tag);
			serializer.text(Integer.toString(i));
			serializer.endTag(null, tag);
		} else {
			serializer.text(Integer.toString(i));
		}
	}

	public void serializeLongElem(long l, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		if (null != tag) {
			serializer.startTag(null, tag);
			serializer.text(Long.toString(l));
			serializer.endTag(null, tag);
		} else {
			serializer.text(Long.toString(l));
		}
	}
	/**
	 * a wrapper method for serializeLongElem(long l, String tag)
	*/
	public void serializeAmountElem(long l, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		 serializeLongElem(l, tag);
	}
	
	public void serializeDoubleElem(double d, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		if (null != tag) {
			serializer.startTag(null, tag);
			serializer.text(Double.toString(d));
			serializer.endTag(null, tag);
		} else {
			serializer.text(Double.toString(d));
		}
	}

	public void serializeBooleanElem(Boolean b, String tag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		if (null != tag) {
			serializer.startTag(null, tag);
			serializer.text(Boolean.toString(b));
			serializer.endTag(null, tag);
		} else {
			serializer.text(Boolean.toString(b));
		}
	}

	protected void serializeEnvelope() throws IllegalArgumentException,
			IllegalStateException, IOException {

		// ResponseObject responseObject = null;
		switch (this.soapVersion) {
		case EfficientSoapDeserializer.VERSION10:
			soapNamespaceURI = EfficientSoapDeserializer.ENVELOPE;
			XMLSchemaURI = EfficientSoapDeserializer.XSD1999;
			XMLSchemaInstanceURI = EfficientSoapDeserializer.XSI1999;
			encodingURI = EfficientSoapDeserializer.ENCODING; 
		case EfficientSoapDeserializer.VERSION11:
			soapNamespaceURI = EfficientSoapDeserializer.ENVELOPE;
			XMLSchemaURI = EfficientSoapDeserializer.XSD;
			XMLSchemaInstanceURI = EfficientSoapDeserializer.XSI;
			encodingURI = EfficientSoapDeserializer.ENCODING;
			break;
		case EfficientSoapDeserializer.VERSION12:
			soapNamespaceURI = EfficientSoapDeserializer.ENVELOPE0503;
			XMLSchemaURI = EfficientSoapDeserializer.XSD;
			XMLSchemaInstanceURI = EfficientSoapDeserializer.XSI;
			encodingURI = EfficientSoapDeserializer.ENCODING0503;
			break;
		default:
			soapNamespaceURI = EfficientSoapDeserializer.ENVELOPE;
			XMLSchemaURI = EfficientSoapDeserializer.XSD;
			XMLSchemaInstanceURI = EfficientSoapDeserializer.XSI;
			encodingURI = EfficientSoapDeserializer.ENCODING;
			break;
		}


		// serialize envelope with Header and Body
		
		//set SOAP specific Namespaces and Prefixes 
		serializer.setPrefix("enc", getEncodingURI());
		serializer.setPrefix("xsd", getXMLSchemaURI());
		serializer.setPrefix("xsi", getXMLSchemaInstanceURI());
		serializer.setPrefix("soap", getSoapNamespaceURI());
		//set domain specific Namespaces and Prefixes
		addDomainNamespacesToEnv();
		
		serializer.startTag(soapNamespaceURI, "Envelope");
		
		//serializeAttributes(serializer, xmlAttributes);
		// serializer.attribute(soapNamespace, name, value);
		//serializer.text(headerSerializer.serializeHeader());
		headerSerializer.serializeHeader();
		bodySerializer.serializeBody((T) paramList);//(object2serialize);
		serializer.endTag(soapNamespaceURI, "Envelope");//check also if the end tag and namespace are matching to them from start tag (case sensitive) 

	}

	private class DefaultHeaderSerializerImpl implements IHeaderSerializer {
		@Override
		public String serializeHeader() {
			return "";
		}

	}

	/**
	 * @author Christian Rau
	 * 
	 *         This class implements an empty soap body for default setting.
	 *         Implement the interface IBodySerializer<T> for your own
	 *         implementation and use setBodySerializer(IBodySerializer<T>
	 *         bodySerializer) to replace this default implementation.
	 */
	private class DefaultBodySerializerImpl implements IBodySerializer<T> {
		@Override
		public void serializeBody(T t) throws IllegalArgumentException,
				IllegalStateException, IOException {
			serializer.startTag(soapNamespaceURI, "Body");
			serializer.text("");
			serializer.endTag(soapNamespaceURI, "Body");
		}
	}

	public void serializeAttributes(XmlSerializer serializer,
			List<XmlAttribute> xmlAttributes) throws IllegalArgumentException,
			IllegalStateException, IOException {
		for (XmlAttribute xmlAttribute : xmlAttributes) {
			if(xmlAttribute.getNamespacePrefix() != null){
			}
			serializer.attribute(xmlAttribute.getNamespace(),
					xmlAttribute.getName(), xmlAttribute.getValue());
		}
	}
	
	// calling a List serializing method needs generic List types.
	// May you have to check your target objects or
	// add an @SuppressWarnings("unchecked") entry
	// at the method witch does the call
	public void serializeStringList(List<String> stringList,
			String listElementTag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		Log.d(APP_LOGGING_TAG, "serializeList(" + listElementTag + ")");
		if (null != listElementTag) {
			for (String str : stringList) {
				serializer.startTag(null, listElementTag);
				serializer.text(str);
				serializer.endTag(null, listElementTag);
			}
		} else {
			for (String str : stringList) {
				serializer.text(str);
			}
		}
	}

	// calling a List serializing method needs generic List types.
	// May you have to check your target objects or
	// add an @SuppressWarnings("unchecked") entry
	// at the method witch does the call
	public void serializeIntegerList(List<Integer> integerList,
			String listElementTag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		Log.d(APP_LOGGING_TAG, "serializeList(" + listElementTag + ")");
		if (null != listElementTag) {
			for (Integer i : integerList) {
				serializer.startTag(null, listElementTag);
				serializer.text(Integer.toString(i));
				serializer.endTag(null, listElementTag);
			}
		} else {
			for (Integer i : integerList) {
				serializer.text(Integer.toString(i));
			}
		}
	}
	
	// calling a List serializing method needs generic List types.
	// May you have to check your target objects or
	// add an @SuppressWarnings("unchecked") entry
	// at the method witch does the call
	public void serializeLongList(List<Long> longList, String listElementTag)
			throws IOException, IllegalArgumentException, IllegalStateException {
		Log.d(APP_LOGGING_TAG, "serializeList(" + listElementTag + ")");
		if (null != listElementTag) {
			for (Long l : longList) {
				serializer.startTag(null, listElementTag);
				serializer.text(Long.toString(l));
				serializer.endTag(null, listElementTag);
			}
		} else {
			for (Long l : longList) {
				serializer.text(Long.toString(l));
			}
		}
	}

	// calling a List serializing method needs generic List types.
	// May you have to check your target objects or
	// add an @SuppressWarnings("unchecked") entry
	// at the method witch does the call
	public void serializeDoubleList(List<Double> doubleList,
			String listElementTag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		Log.d(APP_LOGGING_TAG, "serializeList(" + listElementTag + ")");
		if (null != listElementTag) {
			for (Double d : doubleList) {
				serializer.startTag(null, listElementTag);
				serializer.text(Double.toString(d));
				serializer.endTag(null, listElementTag);
			}
		} else {
			for (Double d : doubleList) {
				serializer.text(Double.toString(d));
			}
		}
	}

	// calling a List serializing method needs generic List types.
	// May you have to check your target objects or
	// add an @SuppressWarnings("unchecked") entry
	// at the method witch does the call
	public void serializeBooleanList(List<Boolean> booleanList,
			String listElementTag) throws IOException,
			IllegalArgumentException, IllegalStateException {
		Log.d(APP_LOGGING_TAG, "serializeList(" + listElementTag + ")");
		if (null != listElementTag) {
			for (Boolean b : booleanList) {
				serializer.startTag(null, listElementTag);
				serializer.text(Boolean.toString(b));
				serializer.endTag(null, listElementTag);
			}
		} else {
			for (Boolean b : booleanList) {
				serializer.text(Boolean.toString(b));
			}
		}
	}

}
